﻿#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

#include <stdint.h>
#include <math.h>
#include <algorithm>
#include <vector>
#include <limits>
#include <random>

#include "../raytracer/scene.h"
#include "../raytracer/light.h"
#include "../raytracer/camera.h"
#include "../raytracer/point_kernel.h"
#include "../raytracer/shade_model.h"

#include "../math/vec3f.h"
#include "../math/pnt3f.h"

#include "windows_utils.h"

#ifdef MICROSOFT_PPL
#include <ppl.h>
#endif

template<class kernel_type>
void invoke_ray_tracing(int first, int size, kernel_type& kernel)
{
	#ifdef MICROSOFT_PPL
	concurrency::parallel_for(first, size, kernel);
	#else
	for(int i = first; i < size; ++i)
		kernel(i);
	#endif
}

LRESULT CALLBACK WindowProc(HWND window, unsigned int message, WPARAM wParam, LPARAM lParam)
{
	if (message == WM_CLOSE)
		PostQuitMessage(0);

	return DefWindowProc(window, message, wParam, lParam);
}

int APIENTRY WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow)
{
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

	//1. Create window
	const wchar_t* className = L"mywindow";
	const wchar_t* name = L"mywindow";

	RegisterClass(className, hInstance, WindowProc);

	const int width = 600;
	const int height = 600;
	
	HWND window = CreateWindow(className, name, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_VISIBLE, 
		0, 0, width, height, NULL, NULL, NULL, 0);

	//2. Create canvas
	std::vector<uint32_t> pixels(width * height);

	//3. Create scene
	ray_tracing::scene_t scene;
	
	auto copper = scene.add_material(vec3fc(0.19125f, 0.0735f, 0.0225f), vec3fc(0.7038f, 0.27048f, 0.0828f),
		vec3fc(0.256777f, 0.137622f, 0.086014f), 12.8f, 0, 0.5f, 0.1f);

	auto gold = scene.add_material(vec3fc(0.24725f, 0.1995f, 0.0745f), vec3fc(0.74164f, 0.60648f, 0.22648f),
		vec3fc(0.628281f, 0.555802f, 0.366065f),
		51.2f,0, 0.5f, 0.1f);

	auto ruby = scene.add_material(vec3fc(0.1745f, 0.01175f, 0.01175f), vec3fc(0.61424f, 0.04136f, 0.04136f),
		vec3fc(0.727811f, 0.626959f, 0.626959f),
		0.6f,0, 0.5f, 0.1f);

	auto bronze = scene.add_material(vec3fc(0.2125f, 0.1275f, 0.054f), vec3fc(0.714f, 0.4284f, 0.18144f),
		vec3fc(0.393548f, 0.271906f, 0.166721f),
		0.2f,0, 0.5f, 0.1f);
	
	scene.add_plane(vec3fc(0, 1, 0), 0, copper);
	scene.add_sphere(pnt3fc(0, 10, 0), 5, copper);
	scene.add_sphere(pnt3fc(10, 10, 0), 8, ruby);
	scene.add_sphere(pnt3fc(20, 10, 50), 9, gold);
	scene.add_sphere(pnt3fc(-20, 10, 50), 2, ruby);
	scene.add_sphere(pnt3fc(40, 60, 50), 4, gold);
			
	auto l1 = scene.add_light(pnt3fc(285, 290, 70), vec3fc(1, 1, 1));
	auto l2 = scene.add_light(pnt3fc(10, 270, 70), vec3fc(1, 1, 1));
	auto l3 = scene.add_light(pnt3fc(70, 200, 30), vec3fc(1, 1, 1));

	vec3f up = { 0, 1, 0 };
	pnt3f pos = { 0, 60, -100 };
	vec3f look_at = { 0, 0, 0 };
				
	auto camera = scene.add_camera(L"camera1",1.f, 4.f, pos, up, look_at);
		
	//4. Setup point kernel
	ray_tracing::point_kernel_t kernel(pixels.data(), width, height, &scene, 
		ray_tracing::shade);

	while (true)
	{
		MSG msg;

		while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);

			if (msg.message == WM_QUIT)
				return 0;
		}

		//5. call kernel for each pixel of canvas
		invoke_ray_tracing(0, width*height, kernel);

		BlitBuffer(pixels.data(), width, height, window);
	}

	CloseHandle(window);

	return 0;
}